package com.defangit.production.orm;

import com.defangit.production.bll.ProductionProcessBll;
import com.defangit.production.domain.Operator;
import com.defangit.production.domain.ProductionBill;
import com.defangit.production.domain.ProductionProcess;
import com.defangit.production.domain.WorkShop;
import laogen.hibernate.CommonFinder;
import laogen.hibernate.CommonService;
import laogen.lang.exception.ParameterNotValidException;
import laogen.util.UniqueNoBuilder;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * 生产工序服务
 */
public class ProductionProcessService extends CommonService<ProductionProcess> implements ProductionProcessBll {
    private static UniqueNoBuilder noBuilder = UniqueNoBuilder.getInstance("production.process", (nos) -> {
        List<String> exists = HibernateUtil
                .INSTANCE
                .getSessionFactory()
                .getCurrentSession()
                .createCriteria(ProductionProcess.class)
                .add(Restrictions.in("no", nos))
                .setProjection(Projections.property("no"))
                .list();
        nos.removeAll(exists);
    });
    /**
     * 添加工序派工单
     * @param billId  工序派工单所属的生产订单id
     * @param process 要添加的工序派工单
     * @return 添加后的工序派工单
     */
    @Override
    public ProductionProcess addProductionProcess(int billId, ProductionProcess process) {
        ProductionBill bill=getSession().get(ProductionBill.class,billId);
        if(bill==null) throw ParameterNotValidException.zeroIdOrEntityNonExist("生产订单id");
        WorkShop workShop = new WorkShopService().get(process.getWorkShop().getId()).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("工单车间信息异常"));
        if (workShop.isNotUsed()) throw ParameterNotValidException.constraintNotSatisfy("该车间为非可用状态","车间使用状态异常,换其他车间或联系管理员");
        bill.getProcesses().add(process);
        bill.setNo(noBuilder.next());
        getSession().beginTransaction();
        getSession().save(bill);
        commitSession();
        return get(process.getId()).orElseThrow(()->
            ParameterNotValidException.zeroIdOrEntityNonExist("工序id"+process.getId())
        );
    }

    @Override
    public ProductionProcess addWorkersProcess(Integer processId,List<Integer> workerIds) {
        if (workerIds.isEmpty()) throw ParameterNotValidException.nullField("工人信息为空");
        ProductionProcess productionProcessm = get(processId)
                .orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("生产订单信息异常"));
        List<Operator> operators = new OperatorService().getOperators(workerIds);
        if (operators.isEmpty()) return productionProcessm;
        productionProcessm.getWorkers().addAll(operators);
        update(productionProcessm);
        commitSession();
        return productionProcessm;
    }

    @Override
    public ProductionProcess updateProductionProcess(ProductionProcess process) {
        ProductionProcess targetProcess = get(process.getId()).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("工序订单信息异常"));
        if (process.getWorkers()!=null&&!process.getWorkers().isEmpty()) {
            targetProcess.setWorkers(process.getWorkers());
        }
        if (process.getProcessSection()!=null) {
            targetProcess.setProcessSection(process.getProcessSection());
        }
        if (process.getItem()!=null) {
            targetProcess.setItem(process.getItem());
        }
        if (process.getLiable()!=null) {
            targetProcess.setLiable(process.getLiable());
        }
        if (process.getWorkShop()!=null) {
            targetProcess.setWorkShop(process.getWorkShop());
        }
        targetProcess.setExpectEndTime(process.getExpectEndTime());
        targetProcess.setExpectStartTime(process.getExpectStartTime());
        update(targetProcess);
        commitSession();
        return get(process.getId()).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("异常的工序订单"));
    }


    public ProductionProcess updateWorkShop(Integer processId, Integer workShopId) {
        ProductionProcess productionProcess = get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("工单信息异常"));
        WorkShop workShop = new WorkShopService().get(workShopId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("车间信息异常"));
        productionProcess.setWorkShop(workShop);
        update(productionProcess);
        commitSession();
        return get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("工单信息异常"));
    }

    @Override
    public boolean deleteProductionProcess(int processId) {
        return false;
    }

    /**
     * 工序负责人开启工序
     *
     * @param liableId  负责人id
     * @param processId 要开启的工序id
     * @param operators 工序涉及的生产工人
     * @return 是否成功开启工序
     */
    @Override
    public boolean beginProcess(int liableId, int processId, Set<Integer> operators) {
        ProductionProcess process=get(processId).orElseThrow(()->
            ParameterNotValidException.zeroIdOrEntityNonExist("工序id")
        );
        if(process.getLiable().getId()!=liableId){
            throw ParameterNotValidException.constraintNotSatisfy("负责人","只有工序负责人可以开启工序");
        };
        process.setStartTime(LocalDateTime.now());
        update(process);
        commitSession();
        return true;
    }

    /**
     * 工序负责人结束并移交工序完工产品
     *
     * @param liableId        负责人id
     * @param sourceProcessId 要结束的工序id
     * @param targetProcessId 移交的目标工序
     * @return 是否成功结束工序
     */
    @Override
    public boolean endProcess(int liableId, int sourceProcessId, int targetProcessId) {
        ProductionProcess process=get(sourceProcessId).orElseThrow(()->
                ParameterNotValidException.zeroIdOrEntityNonExist("工序id")
        );
        if(process.getLiable().getId()!=liableId){
            throw ParameterNotValidException.constraintNotSatisfy("负责人","只有工序负责人可以开启工序");
        };
        process.setEndTime(LocalDateTime.now());
        update(process);
        commitSession();
        return true;
    }

    /**
     * 被移交工序负责人确认接手工序
     *
     * @param liableId  负责人id
     * @param processId 要接手的工序id
     * @return 是否成功接手工序
     */
    @Override
    public boolean acceptProcess(int liableId, int processId) {
        return false;
    }

    @Override
    public ProductionProcess updateExpectStartTime(LocalDateTime expectTime, Integer processId, Integer liableId) {
        Operator liable = new OperatorService().get(liableId).orElseThrow(()-> ParameterNotValidException.zeroIdOrEntityNonExist("委派工单负责人信息异常,无法更改计划时间"));
        ProductionProcess productionProcess = get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("异常的委派工单"));
        if (productionProcess.getEndTime()!=null) throw ParameterNotValidException.constraintNotSatisfy("已完成的委派工单不可更改计划时间","this time is not update");
        ProductionBill productionBill = new ProductionBillService().processId(processId);
        if (expectTime.isBefore(productionBill.getStartTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划开启时间不能在生产计划的开启时间之前","this time is invalid");
        if (expectTime.isAfter(productionBill.getExpectEndTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划开启时间不能在该计划的完成时间之后","this time is invalid");
        if (productionProcess.getExpectEndTime()!=null&&expectTime.isAfter(productionProcess.getExpectEndTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划开启时间在计划完成时间之后,请先重新设置计划完成时间","this time is invalid");
        if (liable.getId()==productionProcess.getLiable().getId()){
            productionProcess.setExpectStartTime(expectTime);
        }else {
            if (liable.getId()==productionBill.getLiable().getId()){
                productionProcess.setExpectStartTime(expectTime);
            }else {
                throw ParameterNotValidException.constraintNotSatisfy("用户权限不足无法修改计划时间","the user can't update this expect");
            }
        }
        update(productionProcess);
        commitSession();
        return get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("获取委派工单信息失败"));
    }

    @Override
    public ProductionProcess updateExpectEndTime(LocalDateTime expectTime, Integer processId, Integer liableId) {
        Operator liable = new OperatorService().get(liableId).orElseThrow(()-> ParameterNotValidException.zeroIdOrEntityNonExist("委派工单负责人信息异常,无法更改计划时间"));
        ProductionProcess productionProcess = get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("异常的委派工单"));
        if (productionProcess.getEndTime()!=null) throw ParameterNotValidException.constraintNotSatisfy("已完成的订单不可更改计划时间","this time is invalid");
        ProductionBill productionBill = new ProductionBillService().processId(processId);
        if (expectTime.isBefore(productionBill.getStartTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划完成时间不能在生产计划的开启时间之前","this time is invalid");
        if (expectTime.isAfter(productionBill.getExpectEndTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划完成时间不能在生产计划完成时间之后","this time is invalid");
        if (productionProcess.getExpectStartTime()!=null&&expectTime.isBefore(productionProcess.getExpectStartTime())) throw ParameterNotValidException.constraintNotSatisfy("委派工单的计划完成时间不能在该计划的开启时间之前","this time is invalid");
        if (liable.getId()==productionProcess.getLiable().getId()){
            productionProcess.setExpectEndTime(expectTime);
        }else {
            if (liable.getId()==productionBill.getLiable().getId()){
                productionProcess.setExpectEndTime(expectTime);
            }else {
                throw ParameterNotValidException.constraintNotSatisfy("用户权限不足无法修改计划时间","the user can't update this expect");
            }
        }
        update(productionProcess);
        commitSession();
        return get(processId).orElseThrow(()->ParameterNotValidException.zeroIdOrEntityNonExist("获取委派工单信息失败"));
    }

    public Optional<ProductionProcess> get(int id){
        if (id == 0) {
            return Optional.empty();
        } else {
            ProductionProcess productionProcess = (ProductionProcess) this.detachedAll()
                    .setFetchMode("workers", FetchMode.JOIN)
                    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                    .add(Restrictions.eq("id",id))
                    .getExecutableCriteria(getSession())
                    .uniqueResult();
            return Optional.ofNullable(productionProcess);
        }
    }

    @Override
    public ProductionProcessFinder findAll() {
        return new ProductionProcessFinderImpl();
    }

    public class ProductionProcessFinderImpl extends CommonFinder<ProductionProcess> implements ProductionProcessFinder{
        private DetachedCriteria criteria;
        ProductionProcessFinderImpl(){
            this.criteria=detachedAll();
        }
        @Override
        public Criteria getCriteria() {
            return criteria.getExecutableCriteria(getSession()).addOrder(Order.desc("id"));
        }

        @Override
        public DetachedCriteria getDetachedCriteria() {
            return criteria;
        }

        /**
         * 根据产品工序状态筛选产品工序
         *
         * @param status 工序状态 0：未开启 1：进行中 2：已完成
         * @return 筛选完工状态后的产品工序查询
         */
        @Override
        public ProductionProcessFinder status(Integer status) {
            if(status==null) return this;
            switch (status){
                case 0:
                    criteria=criteria.add(Restrictions.isNull("startTime"));
                    return this;
                case 1:
                    criteria=criteria.add(Restrictions.and(
                            Restrictions.isNotNull("startTime"),
                            Restrictions.isNull("endTime")
                    ));
                    return this;
                case 2:
                    criteria=criteria.add(Restrictions.isNotNull("endTime"));
                    return this;
                default:
                    return this;
            }
        }

        /**
         * 根据负责人筛选产品工序
         *
         * @param liableId 要查询的负责人id
         * @return 筛选负责人后的产品工序查询
         */
        @Override
        public ProductionProcessFinder liable(Integer liableId) {
            if(liableId==null) return this;
            criteria=criteria.add(Restrictions.eq("liable.id",liableId));
            return this;
        }

        @Override
        public ProductionProcessFinder workerId(Integer id) {
            if (id==null) return this;
            criteria = criteria.createAlias("workers","worker").add(Restrictions.eq("worker.id",id));
            return this;
        }
    }
}
